 /*******************************************************************************
  * Copyright (c) 2004, 2006 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/
 package org.eclipse.core.commands;

 import java.util.ArrayList ;
 import java.util.Collections ;
 import java.util.HashMap ;
 import java.util.HashSet ;
 import java.util.Iterator ;
 import java.util.Map ;
 import java.util.Set ;
 import java.util.WeakHashMap ;

 import org.eclipse.core.commands.common.HandleObjectManager;
 import org.eclipse.core.commands.common.NotDefinedException;
 import org.eclipse.core.runtime.ListenerList;

 /**
  * <p>
  * A central repository for commands -- both in the defined and undefined
  * states. Commands can be created and retrieved using this manager. It is
  * possible to listen to changes in the collection of commands by attaching a
  * listener to the manager.
  * </p>
  *
  * @see CommandManager#getCommand(String)
  * @since 3.1
  */
 public final class CommandManager extends HandleObjectManager implements
         ICategoryListener, ICommandListener, IParameterTypeListener {

     /**
      * A listener that forwards incoming execution events to execution listeners
      * on this manager. The execution events will come from any command on this
      * manager.
      *
      * @since 3.1
      */
     private final class ExecutionListener implements
             IExecutionListenerWithChecks {

         public void notDefined(String commandId, NotDefinedException exception) {
             if (executionListeners != null) {
                 final Object [] listeners = executionListeners.getListeners();
                 for (int i = 0; i < listeners.length; i++) {
                     final Object object = listeners[i];
                     if (object instanceof IExecutionListenerWithChecks) {
                         final IExecutionListenerWithChecks listener = (IExecutionListenerWithChecks) object;
                         listener.notDefined(commandId, exception);
                     }
                 }
             }
         }

         public void notEnabled(String commandId, NotEnabledException exception) {
             if (executionListeners != null) {
                 final Object [] listeners = executionListeners.getListeners();
                 for (int i = 0; i < listeners.length; i++) {
                     final Object object = listeners[i];
                     if (object instanceof IExecutionListenerWithChecks) {
                         final IExecutionListenerWithChecks listener = (IExecutionListenerWithChecks) object;
                         listener.notEnabled(commandId, exception);
                     }
                 }
             }
         }

         public final void notHandled(final String commandId,
                 final NotHandledException exception) {
             if (executionListeners != null) {
                 final Object [] listeners = executionListeners.getListeners();
                 for (int i = 0; i < listeners.length; i++) {
                     final Object object = listeners[i];
                     if (object instanceof IExecutionListener) {
                         final IExecutionListener listener = (IExecutionListener) object;
                         listener.notHandled(commandId, exception);
                     }
                 }
             }
         }

         public final void postExecuteFailure(final String commandId,
                 final ExecutionException exception) {
             if (executionListeners != null) {
                 final Object [] listeners = executionListeners.getListeners();
                 for (int i = 0; i < listeners.length; i++) {
                     final Object object = listeners[i];
                     if (object instanceof IExecutionListener) {
                         final IExecutionListener listener = (IExecutionListener) object;
                         listener.postExecuteFailure(commandId, exception);
                     }
                 }
             }
         }

         public final void postExecuteSuccess(final String commandId,
                 final Object returnValue) {
             if (executionListeners != null) {
                 final Object [] listeners = executionListeners.getListeners();
                 for (int i = 0; i < listeners.length; i++) {
                     final Object object = listeners[i];
                     if (object instanceof IExecutionListener) {
                         final IExecutionListener listener = (IExecutionListener) object;
                         listener.postExecuteSuccess(commandId, returnValue);
                     }
                 }
             }
         }

         public final void preExecute(final String commandId,
                 final ExecutionEvent event) {
             if (executionListeners != null) {
                 final Object [] listeners = executionListeners.getListeners();
                 for (int i = 0; i < listeners.length; i++) {
                     final Object object = listeners[i];
                     if (object instanceof IExecutionListener) {
                         final IExecutionListener listener = (IExecutionListener) object;
                         listener.preExecute(commandId, event);
                     }
                 }
             }
         }
     }

     /**
      * The identifier of the category in which all auto-generated commands will
      * appear. This value must never be <code>null</code>.
      *
      * @since 3.2
      */
     public static final String AUTOGENERATED_CATEGORY_ID = "org.eclipse.core.commands.categories.autogenerated"; //$NON-NLS-1$

     /**
      * The escape character to use for serialization and deserialization of
      * parameterized commands.
      */
     static final char ESCAPE_CHAR = '%';

     /**
      * The character that separates a parameter id from its value.
      */
     static final char ID_VALUE_CHAR = '=';

     /**
      * The character that indicates the end of a list of parameters.
      */
     static final char PARAMETER_END_CHAR = ')';

     /**
      * The character that separators parameters from each other.
      */
     static final char PARAMETER_SEPARATOR_CHAR = ',';

     /**
      * The character that indicates the start of a list of parameters.
      */
     static final char PARAMETER_START_CHAR = '(';

     /**
      * Unescapes special characters in the command id, parameter ids and
      * parameter values for {@link #deserialize()}. The special characters
      * {@link #PARAMETER_START_CHAR}, {@link #PARAMETER_END_CHAR},
      * {@link #ID_VALUE_CHAR}, {@link #PARAMETER_SEPARATOR_CHAR} and
      * {@link #ESCAPE_CHAR} are escaped by prepending an {@link #ESCAPE_CHAR}
      * character.
      *
      * @param escapedText
      * a <code>String</code> that may contain escaped special
      * characters for command serialization.
      * @return a <code>String</code> representing <code>escapedText</code>
      * with any escaped characters replaced by their literal values
      * @throws SerializationException
      * if <code>escapedText</code> contains an invalid escape
      * sequence
      * @see ParameterizedCommand#escape(String)
      * @since 3.2
      */
     private static final String unescape(final String escapedText)
             throws SerializationException {

         // defer initialization of a StringBuffer until we know we need one
 StringBuffer buffer = null;

         for (int i = 0; i < escapedText.length(); i++) {

             char c = escapedText.charAt(i);
             if (c != ESCAPE_CHAR) {
                 // normal unescaped character
 if (buffer != null) {
                     buffer.append(c);
                 }
             } else {
                 if (buffer == null) {
                     buffer = new StringBuffer (escapedText.substring(0, i));
                 }

                 if (++i < escapedText.length()) {
                     c = escapedText.charAt(i);
                     switch (c) {
                     case PARAMETER_START_CHAR:
                     case PARAMETER_END_CHAR:
                     case ID_VALUE_CHAR:
                     case PARAMETER_SEPARATOR_CHAR:
                     case ESCAPE_CHAR:
                         buffer.append(c);
                         break;
                     default:
                         throw new SerializationException(
                                 "Invalid character '" + c + "' in escape sequence"); //$NON-NLS-1$ //$NON-NLS-2$
 }
                 } else {
                     throw new SerializationException(
                             "Unexpected termination of escape sequence"); //$NON-NLS-1$
 }
             }

         }

         if (buffer == null) {
             return escapedText;
         }

         return buffer.toString();
     }

     /**
      * The map of category identifiers (<code>String</code>) to categories (
      * <code>Category</code>). This collection may be empty, but it is never
      * <code>null</code>.
      */
     private final Map categoriesById = new HashMap ();

     /**
      * The set of identifiers for those categories that are defined. This value
      * may be empty, but it is never <code>null</code>.
      */
     private final Set definedCategoryIds = new HashSet ();

     /**
      * The set of identifiers for those command parameter types that are
      * defined. This value may be empty, but it is never <code>null</code>.
      *
      * @since 3.2
      */
     private final Set definedParameterTypeIds = new HashSet ();

     /**
      * The execution listener for this command manager. This just forwards
      * events from commands controlled by this manager to listeners on this
      * manager.
      */
     private IExecutionListener executionListener = null;

     /**
      * The collection of execution listeners. This collection is
      * <code>null</code> if there are no listeners.
      */
     private ListenerList executionListeners = null;

     /**
      * The help context identifiers ({@link String}) for a handler ({@link IHandler}).
      * This map may be empty, but it is never <code>null</code>. Entries are
      * removed if all strong references to the handler are removed.
      *
      * @since 3.2
      */
     private final Map helpContextIdsByHandler = new WeakHashMap ();

     /**
      * The map of parameter type identifiers (<code>String</code>) to
      * parameter types ( <code>ParameterType</code>). This collection may be
      * empty, but it is never <code>null</code>.
      *
      * @since 3.2
      */
     private final Map parameterTypesById = new HashMap ();

     /**
      * Adds a listener to this command manager. The listener will be notified
      * when the set of defined commands changes. This can be used to track the
      * global appearance and disappearance of commands.
      *
      * @param listener
      * The listener to attach; must not be <code>null</code>.
      */
     public final void addCommandManagerListener(
             final ICommandManagerListener listener) {
         addListenerObject(listener);
     }

     /**
      * Adds an execution listener to this manager. This listener will be
      * notified if any of the commands controlled by this manager execute. This
      * can be used to support macros and instrumentation of commands.
      *
      * @param listener
      * The listener to attach; must not be <code>null</code>.
      */
     public final void addExecutionListener(final IExecutionListener listener) {
         if (listener == null) {
             throw new NullPointerException (
                     "Cannot add a null execution listener"); //$NON-NLS-1$
 }

         if (executionListeners == null) {
             executionListeners = new ListenerList(ListenerList.IDENTITY);

             // Add an execution listener to every command.
 executionListener = new ExecutionListener();
             final Iterator commandItr = handleObjectsById.values().iterator();
             while (commandItr.hasNext()) {
                 final Command command = (Command) commandItr.next();
                 command.addExecutionListener(executionListener);
             }

         }

         executionListeners.add(listener);
     }

     /*
      * (non-Javadoc)
      *
      * @see org.eclipse.core.commands.ICategoryListener#categoryChanged(org.eclipse.core.commands.CategoryEvent)
      */
     public final void categoryChanged(CategoryEvent categoryEvent) {
         if (categoryEvent.isDefinedChanged()) {
             final Category category = categoryEvent.getCategory();
             final String categoryId = category.getId();
             final boolean categoryIdAdded = category.isDefined();
             if (categoryIdAdded) {
                 definedCategoryIds.add(categoryId);
             } else {
                 definedCategoryIds.remove(categoryId);
             }
             if (isListenerAttached()) {
                 fireCommandManagerChanged(new CommandManagerEvent(this, null,
                         false, false, categoryId, categoryIdAdded, true));
             }
         }
     }

     /*
      * (non-Javadoc)
      *
      * @see org.eclipse.commands.ICommandListener#commandChanged(org.eclipse.commands.CommandEvent)
      */
     public final void commandChanged(final CommandEvent commandEvent) {
         if (commandEvent.isDefinedChanged()) {
             final Command command = commandEvent.getCommand();
             final String commandId = command.getId();
             final boolean commandIdAdded = command.isDefined();
             if (commandIdAdded) {
                 definedHandleObjects.add(command);
             } else {
                 definedHandleObjects.remove(command);
             }
             if (isListenerAttached()) {
                 fireCommandManagerChanged(new CommandManagerEvent(this,
                         commandId, commandIdAdded, true, null, false, false));
             }
         }
     }

     /**
      * Sets the name and description of the category for uncategorized commands.
      * This is the category that will be returned if
      * {@link #getCategory(String)} is called with <code>null</code>.
      *
      * @param name
      * The name of the category for uncategorized commands; must not
      * be <code>null</code>.
      * @param description
      * The description of the category for uncategorized commands;
      * may be <code>null</code>.
      * @since 3.2
      */
     public final void defineUncategorizedCategory(final String name,
             final String description) {
         final Category category = getCategory(AUTOGENERATED_CATEGORY_ID);
         category.define(name, description);
     }

     /**
      * <p>
      * Returns a {@link ParameterizedCommand} with a command and
      * parameterizations as specified in the provided
      * <code>serializedParameterizedCommand</code> string. The
      * <code>serializedParameterizedCommand</code> must use the format
      * returned by {@link ParameterizedCommand#serialize()} and described in the
      * Javadoc for that method.
      * </p>
      * <p>
      * If a parameter id encoded in the
      * <code>serializedParameterizedCommand</code> does not exist in the
      * encoded command, that parameter id and value are ignored. A given
      * parameter id should not be used more than once in
      * <code>serializedParameterizedCommand</code>. This will not result in
      * an exception, but in this case the value of the parameter when the
      * command is executed is unspecified.
      * </p>
      * <p>
      * This method will never return <code>null</code>, however it may throw
      * an exception if there is a problem processing the serialization string or
      * the encoded command is undefined.
      * </p>
      *
      * @param serializedParameterizedCommand
      * a string representing a command id and parameter ids and
      * values; must not be <code>null</code>
      * @return a {@link ParameterizedCommand} with the command and
      * parameterizations encoded in the
      * <code>serializedParameterizedCommand</code>; never
      * <code>null</code>.
      * @throws NotDefinedException
      * if the command indicated in
      * <code>serializedParameterizedCommand</code> is not defined
      * @throws SerializationException
      * if there is an error deserializing
      * <code>serializedParameterizedCommand</code>
      * @see ParameterizedCommand#serialize()
      * @since 3.2
      */
     public final ParameterizedCommand deserialize(
             final String serializedParameterizedCommand)
             throws NotDefinedException, SerializationException {

         final int lparenPosition = unescapedIndexOf(
                 serializedParameterizedCommand, PARAMETER_START_CHAR);

         final String commandIdEscaped;
         final String serializedParameters;
         if (lparenPosition == -1) {
             commandIdEscaped = serializedParameterizedCommand;
             serializedParameters = null;
         } else {
             commandIdEscaped = serializedParameterizedCommand.substring(0,
                     lparenPosition);

             if (serializedParameterizedCommand
                     .charAt(serializedParameterizedCommand.length() - 1) != PARAMETER_END_CHAR) {
                 throw new SerializationException(
                         "Parentheses must be balanced in serialized ParameterizedCommand"); //$NON-NLS-1$
 }

             serializedParameters = serializedParameterizedCommand.substring(
                     lparenPosition + 1, // skip PARAMETER_START_CHAR
 serializedParameterizedCommand.length() - 1); // skip
 // PARAMETER_END_CHAR
 }

         final String commandId = unescape(commandIdEscaped);
         final Command command = getCommand(commandId);
         final IParameter[] parameters = command.getParameters();
         final Parameterization[] parameterizations = getParameterizations(
                 serializedParameters, parameters);

         return new ParameterizedCommand(command, parameterizations);
     }

     /**
      * Notifies all of the listeners to this manager that the set of defined
      * command identifiers has changed.
      *
      * @param event
      * The event to send to all of the listeners; must not be
      * <code>null</code>.
      */
     private final void fireCommandManagerChanged(final CommandManagerEvent event) {
         if (event == null) {
             throw new NullPointerException ();
         }

         final Object [] listeners = getListeners();
         for (int i = 0; i < listeners.length; i++) {
             final ICommandManagerListener listener = (ICommandManagerListener) listeners[i];
             listener.commandManagerChanged(event);
         }
     }

     /**
      * Returns all of the commands known by this manager -- defined and
      * undefined.
      *
      * @return All of the commands; may be empty, but never <code>null</code>.
      * @since 3.2
      */
     public final Command[] getAllCommands() {
         return (Command[]) handleObjectsById.values().toArray(
                 new Command[handleObjectsById.size()]);
     }

     /**
      * Gets the category with the given identifier. If no such category
      * currently exists, then the category will be created (but be undefined).
      *
      * @param categoryId
      * The identifier to find; must not be <code>null</code>. If
      * the category is <code>null</code>, then a category suitable
      * for uncategorized items is defined and returned.
      * @return The category with the given identifier; this value will never be
      * <code>null</code>, but it might be undefined.
      * @see Category
      */
     public final Category getCategory(final String categoryId) {
         if (categoryId == null) {
             return getCategory(AUTOGENERATED_CATEGORY_ID);
         }

         checkId(categoryId);

         Category category = (Category) categoriesById.get(categoryId);
         if (category == null) {
             category = new Category(categoryId);
             categoriesById.put(categoryId, category);
             category.addCategoryListener(this);
         }

         return category;
     }

     /**
      * Gets the command with the given identifier. If no such command currently
      * exists, then the command will be created (but will be undefined).
      *
      * @param commandId
      * The identifier to find; must not be <code>null</code> and
      * must not be zero-length.
      * @return The command with the given identifier; this value will never be
      * <code>null</code>, but it might be undefined.
      * @see Command
      */
     public final Command getCommand(final String commandId) {
         checkId(commandId);

         Command command = (Command) handleObjectsById.get(commandId);
         if (command == null) {
             command = new Command(commandId);
             handleObjectsById.put(commandId, command);
             command.addCommandListener(this);

             if (executionListener != null) {
                 command.addExecutionListener(executionListener);
             }
         }

         return command;
     }

     /**
      * Returns the categories that are defined.
      *
      * @return The defined categories; this value may be empty, but it is never
      * <code>null</code>.
      * @since 3.2
      */
     public final Category[] getDefinedCategories() {
         final Category[] categories = new Category[definedCategoryIds.size()];
         final Iterator categoryIdItr = definedCategoryIds.iterator();
         int i = 0;
         while (categoryIdItr.hasNext()) {
             String categoryId = (String ) categoryIdItr.next();
             categories[i++] = getCategory(categoryId);
         }
         return categories;
     }

     /**
      * Returns the set of identifiers for those category that are defined.
      *
      * @return The set of defined category identifiers; this value may be empty,
      * but it is never <code>null</code>.
      */
     public final Set getDefinedCategoryIds() {
         return Collections.unmodifiableSet(definedCategoryIds);
     }

     /**
      * Returns the set of identifiers for those commands that are defined.
      *
      * @return The set of defined command identifiers; this value may be empty,
      * but it is never <code>null</code>.
      */
     public final Set getDefinedCommandIds() {
         return getDefinedHandleObjectIds();
     }

     /**
      * Returns the commands that are defined.
      *
      * @return The defined commands; this value may be empty, but it is never
      * <code>null</code>.
      * @since 3.2
      */
     public final Command[] getDefinedCommands() {
         return (Command[]) definedHandleObjects
                 .toArray(new Command[definedHandleObjects.size()]);
     }

     /**
      * Returns the set of identifiers for those parameter types that are
      * defined.
      *
      * @return The set of defined command parameter type identifiers; this value
      * may be empty, but it is never <code>null</code>.
      * @since 3.2
      */
     public final Set getDefinedParameterTypeIds() {
         return Collections.unmodifiableSet(definedParameterTypeIds);
     }

     /**
      * Returns the command parameter types that are defined.
      *
      * @return The defined command parameter types; this value may be empty, but
      * it is never <code>null</code>.
      * @since 3.2
      */
     public final ParameterType[] getDefinedParameterTypes() {
         final ParameterType[] parameterTypes = new ParameterType[definedParameterTypeIds
                 .size()];
         final Iterator iterator = definedParameterTypeIds.iterator();
         int i = 0;
         while (iterator.hasNext()) {
             final String parameterTypeId = (String ) iterator.next();
             parameterTypes[i++] = getParameterType(parameterTypeId);
         }
         return parameterTypes;
     }

     /**
      * Gets the help context identifier for a particular command. The command's
      * handler is first checked for a help context identifier. If the handler
      * does not have a help context identifier, then the help context identifier
      * for the command is returned. If neither has a help context identifier,
      * then <code>null</code> is returned.
      *
      * @param command
      * The command for which the help context should be retrieved;
      * must not be <code>null</code>.
      * @return The help context identifier to use for the given command; may be
      * <code>null</code>.
      * @throws NotDefinedException
      * If the given command is not defined.
      * @since 3.2
      */
     public final String getHelpContextId(final Command command)
             throws NotDefinedException {
         // Check if the command is defined.
 if (!command.isDefined()) {
             throw new NotDefinedException("The command is not defined. " //$NON-NLS-1$
 + command.getId());
         }

         // Check the handler.
 final IHandler handler = command.getHandler();
         if (handler != null) {
             final String helpContextId = (String ) helpContextIdsByHandler
                     .get(handler);
             if (helpContextId != null) {
                 return helpContextId;
             }
         }

         // Simply return whatever the command has as a help context identifier.
 return command.getHelpContextId();
     }

     /**
      * Returns an array of parameterizations for the provided command by
      * deriving the parameter ids and values from the provided
      * <code>serializedParameters</code> string.
      *
      * @param serializedParameters
      * a String encoding parameter ids and values; must not be
      * <code>null</code>.
      * @param parameters
      * array of parameters of the command being deserialized; may be
      * <code>null</code>.
      * @return an array of parameterizations; may be <code>null</code>.
      * @throws NotDefinedException
      * if the command is not defined
      * @throws SerializationException
      * if there is an error deserializing the parameters
      * @since 3.2
      */
     private final Parameterization[] getParameterizations(
             String serializedParameters, final IParameter[] parameters)
             throws SerializationException {

         if (serializedParameters == null
                 || (serializedParameters.length() == 0)) {
             return null;
         }

         if ((parameters == null) || (parameters.length == 0)) {
             return null;
         }

         final ArrayList paramList = new ArrayList ();

         int commaPosition; // split off each param by looking for ','
 do {
             commaPosition = unescapedIndexOf(serializedParameters, ',');

             final String idEqualsValue;
             if (commaPosition == -1) {
                 // no more parameters after this
 idEqualsValue = serializedParameters;
             } else {
                 // take the first parameter...
 idEqualsValue = serializedParameters
                         .substring(0, commaPosition);

                 // ... and put the rest back into serializedParameters
 serializedParameters = serializedParameters
                         .substring(commaPosition + 1);
             }

             final int equalsPosition = unescapedIndexOf(idEqualsValue, '=');

             final String parameterId;
             final String parameterValue;
             if (equalsPosition == -1) {
                 // missing values are null
 parameterId = unescape(idEqualsValue);
                 parameterValue = null;
             } else {
                 parameterId = unescape(idEqualsValue.substring(0,
                         equalsPosition));
                 parameterValue = unescape(idEqualsValue
                         .substring(equalsPosition + 1));
             }

             for (int i = 0; i < parameters.length; i++) {
                 final IParameter parameter = parameters[i];
                 if (parameter.getId().equals(parameterId)) {
                     paramList.add(new Parameterization(parameter,
                             parameterValue));
                     break;
                 }
             }

         } while (commaPosition != -1);

         return (Parameterization[]) paramList
                 .toArray(new Parameterization[paramList.size()]);
     }

     /**
      * Gets the command {@link ParameterType} with the given identifier. If no
      * such command parameter type currently exists, then the command parameter
      * type will be created (but will be undefined).
      *
      * @param parameterTypeId
      * The identifier to find; must not be <code>null</code> and
      * must not be zero-length.
      * @return The {@link ParameterType} with the given identifier; this value
      * will never be <code>null</code>, but it might be undefined.
      * @since 3.2
      */
     public final ParameterType getParameterType(final String parameterTypeId) {
         checkId(parameterTypeId);

         ParameterType parameterType = (ParameterType) parameterTypesById
                 .get(parameterTypeId);
         if (parameterType == null) {
             parameterType = new ParameterType(parameterTypeId);
             parameterTypesById.put(parameterTypeId, parameterType);
             parameterType.addListener(this);
         }

         return parameterType;
     }

     /**
      * {@inheritDoc}
      *
      * @since 3.2
      */
     public final void parameterTypeChanged(
             final ParameterTypeEvent parameterTypeEvent) {
         if (parameterTypeEvent.isDefinedChanged()) {
             final ParameterType parameterType = parameterTypeEvent
                     .getParameterType();
             final String parameterTypeId = parameterType.getId();
             final boolean parameterTypeIdAdded = parameterType.isDefined();
             if (parameterTypeIdAdded) {
                 definedParameterTypeIds.add(parameterTypeId);
             } else {
                 definedParameterTypeIds.remove(parameterTypeId);
             }

             fireCommandManagerChanged(new CommandManagerEvent(this,
                     parameterTypeId, parameterTypeIdAdded, true));
         }
     }

     /**
      * Removes a listener from this command manager.
      *
      * @param listener
      * The listener to be removed; must not be <code>null</code>.
      */
     public final void removeCommandManagerListener(
             final ICommandManagerListener listener) {
         removeListenerObject(listener);
     }

     /**
      * Removes an execution listener from this command manager.
      *
      * @param listener
      * The listener to be removed; must not be <code>null</code>.
      */
     public final void removeExecutionListener(final IExecutionListener listener) {
         if (listener == null) {
             throw new NullPointerException ("Cannot remove a null listener"); //$NON-NLS-1$
 }

         if (executionListeners == null) {
             return;
         }

         executionListeners.remove(listener);

         if (executionListeners.isEmpty()) {
             executionListeners = null;

             // Remove the execution listener to every command.
 final Iterator commandItr = handleObjectsById.values().iterator();
             while (commandItr.hasNext()) {
                 final Command command = (Command) commandItr.next();
                 command.removeExecutionListener(executionListener);
             }
             executionListener = null;

         }
     }

     /**
      * Block updates all of the handlers for all of the commands. If the handler
      * is <code>null</code> or the command id does not exist in the map, then
      * the command becomes unhandled. Otherwise, the handler is set to the
      * corresponding value in the map.
      *
      * @param handlersByCommandId
      * A map of command identifiers (<code>String</code>) to
      * handlers (<code>IHandler</code>). This map may be
      * <code>null</code> if all handlers should be cleared.
      * Similarly, if the map is empty, then all commands will become
      * unhandled.
      */
     public final void setHandlersByCommandId(final Map handlersByCommandId) {
         // Make that all the reference commands are created.
 final Iterator commandIdItr = handlersByCommandId.keySet().iterator();
         while (commandIdItr.hasNext()) {
             getCommand((String ) commandIdItr.next());
         }

         // Now, set-up the handlers on all of the existing commands.
 final Iterator commandItr = handleObjectsById.values().iterator();
         while (commandItr.hasNext()) {
             final Command command = (Command) commandItr.next();
             final String commandId = command.getId();
             final Object value = handlersByCommandId.get(commandId);
             if (value instanceof IHandler) {
                 command.setHandler((IHandler) value);
             } else {
                 command.setHandler(null);
             }
         }
     }

     /**
      * Sets the help context identifier to associate with a particular handler.
      *
      * @param handler
      * The handler with which to register a help context identifier;
      * must not be <code>null</code>.
      * @param helpContextId
      * The help context identifier to register; may be
      * <code>null</code> if the help context identifier should be
      * removed.
      * @since 3.2
      */
     public final void setHelpContextId(final IHandler handler,
             final String helpContextId) {
         if (handler == null) {
             throw new NullPointerException ("The handler cannot be null"); //$NON-NLS-1$
 }
         if (helpContextId == null) {
             helpContextIdsByHandler.remove(handler);
         } else {
             helpContextIdsByHandler.put(handler, helpContextId);
         }
     }

     /**
      * Searches for the index of a <code>char</code> in a <code>String</code>
      * but disregards characters prefixed with the {@link #ESCAPE_CHAR} escape
      * character. This is used by {@link #deserialize(String)} and
      * {@link #getParameterizations(String, IParameter[])} to parse the
      * serialized parameterized command string.
      *
      * @param escapedText
      * the string to search for the index of <code>ch</code> in
      * @param ch
      * a character to search for in <code>escapedText</code>
      * @return the index of the first unescaped occurrence of the character in
      * <code>escapedText</code>, or <code>-1</code> if the
      * character does not occur unescaped.
      * @see String#indexOf(int)
      */
     private final int unescapedIndexOf(final String escapedText, final char ch) {

         int pos = escapedText.indexOf(ch);

         // first char can't be escaped
 if (pos == 0) {
             return pos;
         }

         while (pos != -1) {
             // look back for the escape character
 if (escapedText.charAt(pos - 1) != ESCAPE_CHAR) {
                 return pos;
             }

             // scan for the next instance of ch
 pos = escapedText.indexOf(ch, pos + 1);
         }

         return pos;

     }

 }

